{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0d82d6cf-6c76-48df-a12e-15055a95bbda",
   "metadata": {},
   "source": [
    "拥有多个相关的模型是很常见的。\n",
    "\n",
    "对用户模型来说尤其如此，因为：\r\n",
    "\r\n",
    "输入模型需要拥有密码属性。\r\n",
    "输出模型不应该包含密码。\r\n",
    "数据库模型很可能需要保存密码的\n",
    "\n",
    "下面是应该如何根据它们的密码字段以及使用位置去定义模型的大概思路：哈希值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "3a67e051-35ee-4bea-aea3-60a4b4d515e4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [7104]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "User saved! ..not really\n",
      "INFO:     127.0.0.1:56124 - \"POST /user/ HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [7104]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel, EmailStr\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "\n",
    "class UserIn(BaseModel):\n",
    "    username: str\n",
    "    password: str\n",
    "    email: EmailStr\n",
    "    full_name: str | None = None\n",
    "\n",
    "\n",
    "class UserOut(BaseModel):\n",
    "    username: str\n",
    "    email: EmailStr\n",
    "    full_name: str | None = None\n",
    "\n",
    "\n",
    "class UserInDB(BaseModel):\n",
    "    username: str\n",
    "    hashed_password: str\n",
    "    email: EmailStr\n",
    "    full_name: str | None = None\n",
    "\n",
    "\n",
    "def fake_password_hasher(raw_password: str):\n",
    "    return \"supersecret\" + raw_password\n",
    "\n",
    "\n",
    "def fake_save_user(user_in: UserIn):\n",
    "    hashed_password = fake_password_hasher(user_in.password)\n",
    "    user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)\n",
    "    print(\"User saved! ..not really\")\n",
    "    return user_in_db\n",
    "\n",
    "\n",
    "@app.post(\"/user/\", response_model=UserOut)\n",
    "async def create_user(user_in: UserIn):\n",
    "    user_saved = fake_save_user(user_in)\n",
    "    return user_saved\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b51fe2d-7bd0-4356-9922-510b562fa981",
   "metadata": {},
   "source": [
    "## requests库"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6bef8953-0685-445e-8aac-370974741a1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/user/' \n",
    "data = {\n",
    "    \"username\": \"Foo\",\n",
    "    \"password\": \"1234321\",\n",
    "    \"email\": \"example@example.com\"\n",
    "}\n",
    "res = requests.post(url, json=data) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"username\":\"Foo\",\"email\":\"example@example.com\",\"full_name\":null}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e01e21a-b160-4431-b023-40d93d3bb541",
   "metadata": {},
   "source": [
    "关于 **user_in.model_dump()\n",
    "Pydantic 的 .model_dump()\n",
    "user_in 是一个 UserIn 类的 Pydantic 模型.\n",
    "\n",
    "Pydantic 模型具有 .model_dump（） 方法，该方法返回一个拥有模型数据的 dict。\n",
    "\n",
    "因此，如果我们像下面这样创建一个 Pydantic 对象 user_in：\n",
    "\n",
    "user_in = UserIn(username=\"john\", password=\"secret\", email=\"john.doe@example.com\")\n",
    "\n",
    "然后我们调用：\n",
    "\n",
    "user_dict = user_in.model_dump()\n",
    "\n",
    "现在我们有了一个数据位于变量 user_dict 中的 dict（它是一个 dict 而不是 Pydantic 模型对象）。\r\n",
    "\r\n",
    "如果我们调\n",
    "\n",
    "print(user_dict)\n",
    "\n",
    "我们将获得一个这样的 Python dict：\n",
    "\n",
    "{\r\n",
    "    'username': 'john',\r\n",
    "    'password': 'secret',\r\n",
    "    'email': 'john.doe@example.com',\r\n",
    "    'full_name': None,\r\n",
    "}用："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7ba1570-84b3-47b5-961c-0137ce73e835",
   "metadata": {},
   "source": [
    "## 解包 dict"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fa0725c-9283-4f69-986b-9f8eab37fe6b",
   "metadata": {},
   "source": [
    "如果我们将 user_dict 这样的 dict 以 **user_dict 形式传递给一个函数（或类），Python将对其进行「解包」。\n",
    "\n",
    "它会将 user_dict 的键和值作为关键字参数直接传递。\n",
    "\n",
    "因此，从上面的 user_dict 继续，编写：\n",
    "\n",
    "UserInDB(**user_dict)\n",
    "\n",
    "会产生类似于以下的结果：\n",
    "\n",
    "UserInDB(\r\n",
    "    username=\"john\",\r\n",
    "    password=\"secret\",\r\n",
    "    email=\"john.doe@example.com\",\r\n",
    "    full_name=Non\n",
    "\n",
    "或者更确切地，直接使用 user_dict 来表示将来可能包含的任何内容\n",
    "\n",
    "UserInDB(\r\n",
    "    username = user_dict[\"username\"],\r\n",
    "    password = user_dict[\"password\"],\r\n",
    "    email = user_dict[\"email\"],\r\n",
    "    full_name = user_dict[\"full_name\"],\r\n",
    ")e,\r\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5215dfd-d8fe-4bb3-ba61-e2fb5271d264",
   "metadata": {},
   "source": [
    "## 来自于其他模型内容的 Pydantic 模型"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95e5537c-3009-4d7b-9196-2ba6cb7fc950",
   "metadata": {},
   "source": [
    "如上例所示，我们从 user_in.model_dump（） 中获得了 user_dict，此代码：\n",
    "\n",
    "user_dict = user_in.model_dump()\r\n",
    "UserInDB(**user_dict\n",
    "\n",
    "等同于：\n",
    "\n",
    "UserInDB(**user_in.model_dump())\n",
    "\n",
    "因为 user_in.model_dump() 是一个 dict，然后我们通过以**开头传递给 UserInDB 来使 Python「解包」它。\r\n",
    "\r\n",
    "这样，我们获得了一个来自于其他 Pydantic 模型中的数据的 Pydantic 模型。)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44d92a33-cffb-4a67-ba9a-65133b77f291",
   "metadata": {},
   "source": [
    "## 解包 dict 和额外关键字"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee1a898a-1c25-4e63-9093-1bebf3e3e368",
   "metadata": {},
   "source": [
    "然后添加额外的关键字参数 hashed_password=hashed_password，例如：\n",
    "\n",
    "UserInDB(**user_in.model_dump(), hashed_password=hashed_password)\n",
    "\n",
    "UserInDB(\r\n",
    "    username = user_dict[\"username\"],\r\n",
    "    password = user_dict[\"password\"],\r\n",
    "    email = user_dict[\"email\"],\r\n",
    "    full_name = user_dict[\"full_name\"],\r\n",
    "    hashed_password = hashed_password,\r\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "833fb015-d19f-4e28-92d6-ca60fb08e324",
   "metadata": {},
   "source": [
    "## 减少重复"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50096ef9-e525-46bd-83e5-1f388594a7fe",
   "metadata": {},
   "source": [
    "减少代码重复是 FastAPI 的核心思想之一。\r\n",
    "\r\n",
    "因为代码重复会增加出现 bug、安全性问题、代码失步问题（当你在一个位置更新了代码但没有在其他位置更新）等的可能性。\r\n",
    "\r\n",
    "上面的这些模型都共享了大量数据，并拥有重复的属性名称和类型。\r\n",
    "\r\n",
    "我们可以做得更好。\r\n",
    "\r\n",
    "我们可以声明一个 UserBase 模型作为其他模型的基类。然后我们可以创建继承该模型属性（类型声明，校验等）的子类。\r\n",
    "\r\n",
    "所有的数据转换、校验、文档生成等仍将正常运行。\r\n",
    "\r\n",
    "这样，我们可以仅声明模型之间的差异部分（具有明文的 password、具有 hashed_password 以及不包括密码）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "406509fa-964b-4046-bc82-b5b35b0e6a66",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [7104]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "User saved! ..not really\n",
      "INFO:     127.0.0.1:56149 - \"POST /user/ HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [7104]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel, EmailStr\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "\n",
    "class UserBase(BaseModel):\n",
    "    username: str\n",
    "    email: EmailStr\n",
    "    full_name: str | None = None\n",
    "\n",
    "\n",
    "class UserIn(UserBase):\n",
    "    password: str\n",
    "\n",
    "\n",
    "class UserOut(UserBase):\n",
    "    pass\n",
    "\n",
    "\n",
    "class UserInDB(UserBase):\n",
    "    hashed_password: str\n",
    "\n",
    "\n",
    "def fake_password_hasher(raw_password: str):\n",
    "    return \"supersecret\" + raw_password\n",
    "\n",
    "\n",
    "def fake_save_user(user_in: UserIn):\n",
    "    hashed_password = fake_password_hasher(user_in.password)\n",
    "    user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)\n",
    "    print(\"User saved! ..not really\")\n",
    "    return user_in_db\n",
    "\n",
    "\n",
    "@app.post(\"/user/\", response_model=UserOut)\n",
    "async def create_user(user_in: UserIn):\n",
    "    user_saved = fake_save_user(user_in)\n",
    "    return user_saved\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0496f25-3cfc-416a-a22e-212feeae1a0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/user/' \n",
    "data = {\n",
    "    \"username\": \"Foo\",\n",
    "    \"password\": \"1234321\",\n",
    "    \"email\": \"example@example.com\"\n",
    "}\n",
    "res = requests.post(url, json=data) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"username\":\"Foo\",\"email\":\"example@example.com\",\"full_name\":null}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fcec708a-2e45-40fe-bae3-8fcf8e22e950",
   "metadata": {},
   "source": [
    "## Union 模型"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4836d94b-f5ce-457a-a633-84102ff145f5",
   "metadata": {},
   "source": [
    "你可以将一个响应声明为两种类型的 Union，这意味着该响应将是两种类型中的任何一种。\r\n",
    "\r\n",
    "这将在 OpenAPI 中使用 anyOf 进行定义。\r\n",
    "\r\n",
    "为此，请使用标准的 Python 类型提示 typing.U\n",
    "\n",
    "定义一个 Union 类型时，首先包括最详细的类型，然后是不太详细的类型。\n",
    "\n",
    "在下面的示例中，更详细的 PlaneItem 位于 Union[PlaneItem，CarItem] 中的 CarItem 之前。nion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9fa0fa83-da06-4c03-ad95-20ce0a6f4ca0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [7104]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:56273 - \"GET /items/item1 HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:56293 - \"GET /items/item2 HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [7104]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from typing import Union\n",
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "\n",
    "class BaseItem(BaseModel):\n",
    "    description: str\n",
    "    type: str\n",
    "\n",
    "\n",
    "class CarItem(BaseItem):\n",
    "    type: str = \"car\"\n",
    "\n",
    "\n",
    "class PlaneItem(BaseItem):\n",
    "    type: str = \"plane\"\n",
    "    size: int\n",
    "\n",
    "\n",
    "items = {\n",
    "    \"item1\": {\"description\": \"All my friends drive a low rider\", \"type\": \"car\"},\n",
    "    \"item2\": {\n",
    "        \"description\": \"Music is my aeroplane, it's my aeroplane\",\n",
    "        \"type\": \"plane\",\n",
    "        \"size\": 5,\n",
    "    },\n",
    "}\n",
    "\n",
    "\n",
    "@app.get(\"/items/{item_id}\", response_model=Union[PlaneItem, CarItem])\n",
    "async def read_item(item_id: str):\n",
    "    return items[item_id]\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e084bdcf-f7cd-441d-97e5-cfb4baa34206",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/items/item1' \n",
    "res = requests.get(url) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"description\":\"All my friends drive a low rider\",\"type\":\"car\"}'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "537bae6f-b73a-4c79-a5ca-1a390149c537",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/items/item2' \n",
    "res = requests.get(url) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"description\":\"Music is my aeroplane, it\\'s my aeroplane\",\"type\":\"plane\",\"size\":5}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6833a44-071f-4a3a-8399-e597b4102dac",
   "metadata": {},
   "source": [
    "## 模型列表"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a935ad3-e7e9-49a3-9d94-760f2251327f",
   "metadata": {},
   "source": [
    "你可以用同样的方式声明由对象列表构成的响应。\r\n",
    "\r\n",
    "为此，请使用标准的 Python typing.List："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6b3d309b-59b7-49ce-81f9-83b296d38d7e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [7104]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:56319 - \"GET /items/ HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [7104]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "\n",
    "class Item(BaseModel):\n",
    "    name: str\n",
    "    description: str\n",
    "\n",
    "\n",
    "items = [\n",
    "    {\"name\": \"Foo\", \"description\": \"There comes my hero\"},\n",
    "    {\"name\": \"Red\", \"description\": \"It's my aeroplane\"},\n",
    "]\n",
    "\n",
    "\n",
    "@app.get(\"/items/\", response_model=list[Item])\n",
    "async def read_items():\n",
    "    return items\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68c740d1-ffa1-4a23-8ebd-c441c966184d",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/items/' \n",
    "res = requests.get(url) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 \n",
    "# '[{\"name\":\"Foo\",\"description\":\"There comes my hero\"},{\"name\":\"Red\",\"description\":\"It\\'s my aeroplane\"}]'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfe840f4-5acd-4ab4-b2bc-81628277c4b8",
   "metadata": {},
   "source": [
    "## 任意 dict 构成的响应"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c400d6f-d894-4971-a764-32c0ef5f4b5a",
   "metadata": {},
   "source": [
    "你还可以使用一个任意的普通 dict 声明响应，仅声明键和值的类型，而不使用 Pydantic 模型。\r\n",
    "\r\n",
    "如果你事先不知道有效的字段/属性名称（对于 Pydantic 模型是必需的），这将很有用。\r\n",
    "\r\n",
    "在这种情况下，你可以使用 typing.Dict："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1b99ccfc-a8c2-4f18-8793-51a0a49f63b9",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [7104]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:56366 - \"GET /keyword-weights/ HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [7104]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "\n",
    "@app.get(\"/keyword-weights/\", response_model=dict[str, float])\n",
    "async def read_keyword_weights():\n",
    "    return {\"foo\": 2.3, \"bar\": 3.4}\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b00a90d-5bfd-4249-b438-a1e6c3f500f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/keyword-weights/' \n",
    "res = requests.get(url) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"foo\":2.3,\"bar\":3.4}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a32d5b7-6fab-4e94-b0d3-823c5b10e015",
   "metadata": {},
   "source": [
    "## 总结"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf114b6b-8a1d-4839-a59c-a769130fa263",
   "metadata": {},
   "source": [
    "使用多个 Pydantic 模型，并针对不同场景自由地继承。\r\n",
    "\r\n",
    "如果一个实体必须能够具有不同的「状态」，你无需为每个状态的实体定义单独的数据模\n",
    "\n",
    "型。以用户「实体」为例，其状态有包含 password、包含 password_hash 以及不含密码。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
